require 'socket'
require 'observer'

class RubyMudConnection
  include Observable
  attr_reader :running, :next_text
  attr_reader :addr, :port
  
  def initialize(addr, port)
    @comm = {'IAC'=>255.chr, 'DONT'=>254.chr, 'DO'=>253.chr, 'WONT'=>252.chr, 'WILL'=>251.chr, 'SB'=>250.chr, 'SE'=>240.chr, 'GMCP' => 201.chr}
    @commb = @comm.inject({}) {|s,(k,v)| s[v] = k;s}
    
    @addr, @port = addr, port
    @socket = TCPSocket.open(@addr, @port)
    
    @in_comm, @in_comm_type = '', false
    @gmcp_gather, @gmcp = false, ''
  rescue Errno::ETIMEDOUT => e
    reconnect
  end
    
  def reconnect
    loop {
      notify_text("\n- RubyMud: Connection timed out... Trying again in 3 seconds.\n")
      sleep(3)
      begin
        @tn = Net::Telnet::new('Host'=>@addr, 'Port'=>@port)
        break
      rescue Errno::ETIMEDOUT => e
      end
    }
  end
  
  def notify_text(text)
    self.changed
    self.notify_observers(text)
  end
  
  def send_text(text)
    @socket.write(text.chomp+"\n") unless text.nil?
  end
  
  def send_gmcp_packet(text)
    @socket.write(@comm['IAC'] + @comm['SB'] + @comm['GMCP'] + text + @comm['IAC'] + @comm['SE'])
  end
  
  def run
    t = Time.now
    text = ''
    until (b = @socket.recv_nonblock(1)).empty? or b.nil? || (b=="\n" && Time.now-t>=0.02)
      if b == @comm['IAC']
        @in_comm = @commb[b]
      elsif !@in_comm.empty?
        if b == @comm['SE']
          self.notify_text("{gmcp}#{@gmcp}")
          @in_comm, @in_comm_type = '', false
          @gmcp, @gmcp_gather = '', false
        elsif ['WILL', 'WONT', 'DO', 'DONT', 'SB'].include?(@commb[b])
          @in_comm += ' ' + @commb[b]
          @in_comm_type = true
        elsif @in_comm_type
          @in_comm += ' ' + (@commb.has_key?(b) ? @commb[b] : b[0].to_i.to_s)
          if @in_comm == 'IAC WILL GMCP'
            @socket.write(@comm['IAC'] + @comm['DO'] + @comm['GMCP'])
            puts "SUPPORTS GMCP"
            self.send_gmcp_packet('Core.Supports.Set [ "Char 1", "Room 1", "Core 1" ]')
          end
          if @in_comm == 'IAC SB GMCP'
            @gmcp_gather = true
          end
          @in_comm, @in_comm_type = '', false
        end
      elsif @gmcp_gather
        @gmcp += b
      else
        text += b
      end
    end
    return if text.nil? or text.empty?
    text += "\n" if b == "\n"
    self.notify_text(text)
  rescue Errno::EWOULDBLOCK => e
    self.notify_text(text + "\n") unless text.empty?
  rescue Errno::ECONNABORTED => e
  rescue Errno::ECONNRESET => e
    reconnect
  rescue => e
    puts "ERROR -- #{e.inspect}"
    puts e.backtrace
  end
end